home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue58 / EasyWeb / Charts.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-03-14  |  36.6 KB  |  1,221 lines

  1. { *****************************************************
  2.                   ThgCustomChart Component
  3.  
  4.   ThgCustomChart is a custom base class for TXYChart and
  5.   other XY, scatter, line and bar charts.
  6.  
  7.                     Paul Warren
  8.            HomeGrown Software Development
  9.          (c) 1997 Langley British Columbia.
  10.                   (604) 856-6523
  11.            e-mail:  hg_soft@uniserve.com
  12.      Home page: http://users.uniserve.com/~hg_soft
  13.   ***************************************************** }
  14.  
  15. unit Charts;
  16.  
  17. interface
  18.  
  19. uses
  20.   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  21.   Forms, Dialogs, StdCtrls, Math;
  22.  
  23. type
  24.   TTitleStr = string[35];
  25.   TGridType = (gtNone, gtHorz, gtVert, gtBoth);
  26.  
  27.   ThgCustomChart = class(TGraphicControl)
  28.   private
  29.     { private declarations }
  30.     FTitle: TTitleStr;
  31.     FTitleFont: TFont;
  32.     FXLabel: TTitleStr;
  33.     FYLabel: TTitleStr;
  34.     FXYLabelFont: TFont;
  35.     FGrid: TGridType;
  36.     FBackGround: TColor;
  37.     FChartColor: TColor;
  38.     FBorderWidth: integer;
  39.     FMinX: Double;
  40.     FMinY: Double;
  41.     FMaxX: Double;
  42.     FMaxY: Double;
  43.     OffScreen: TBitMap;
  44.     function GetChartRect: TRect; virtual;
  45.     function GetDrawRect: TRect; virtual; abstract;
  46.     procedure SetTitle(Value: TTitleStr);
  47.     procedure SetTitleFont(Value: TFont);
  48.     procedure SetXLabel(Value: TTitleStr);
  49.     procedure SetYLabel(Value: TTitleStr);
  50.     procedure SetXYLabelFont(Value: TFont);
  51.     procedure SetGrid(Value: TGridType);
  52.     procedure SetBackGround(Value: TColor);
  53.     procedure SetChartColor(Value: TColor);
  54.     procedure SetBorderWidth(Value: integer);
  55.   protected
  56.     { protected declarations }
  57.     procedure Paint; override;
  58.     procedure DrawGrid(Rect: TRect); virtual; abstract;
  59.     procedure DrawXScale; virtual; abstract;
  60.     procedure DrawYScale; virtual; abstract;
  61.     procedure DrawDataPoints; virtual; abstract;
  62.     procedure ScaleChart; virtual; abstract;
  63.     property ChartRect: TRect read GetChartRect;
  64.     property DrawRect: TRect read GetDrawRect;
  65.   public
  66.     { public declarations }
  67.     constructor Create(AOwner: TComponent); override;
  68.     destructor Destroy; override;
  69.     procedure PaintTo(DC: HDC; X, Y: Integer);
  70.     function GetComponentImage: TBitmap;
  71.     property Title: TTitleStr read FTitle write SetTitle;
  72.     property TitleFont: TFont read FTitleFont write SetTitleFont;
  73.     property XLabel: TTitleStr read FXLabel write SetXLabel;
  74.     property YLabel: TTitleStr read FYLabel write SetYLabel;
  75.     property XYLabelFont: TFont read FXYLabelFont write SetXYLabelFont;
  76.     property Grid: TGridType read FGrid write SetGrid default gtNone;
  77.     property BackGround: TColor read FBackGround write SetBackGround default clSilver;
  78.     property ChartColor: TColor read FChartColor write SetChartColor default clWhite;
  79.     property BorderWidth: integer read FBorderWidth write SetBorderWidth default 1;
  80.     property MinX: Double read FMinX write FMinX;
  81.     property MaxX: Double read FMaxX write FMaxX;
  82.     property MinY: Double read FMinY write FMinY;
  83.     property MaxY: Double read FMaxY write FMaxY;
  84.   published
  85.     { published declarations }
  86.   end;
  87.  
  88.   TDataPoint = class(TObject)
  89.     X, Y: Double;
  90.     ALabel: string[15];
  91.   end;
  92.  
  93.   TChartDataList = class(TStringList)
  94.   private
  95.     { private declarations }
  96.   public
  97.     { public declarations }
  98.     destructor Destroy; override;
  99.     function AddDataPoint(Point: TDataPoint): integer;
  100.     function GetDataPoint(index: integer): TDataPoint;
  101.   end;
  102.  
  103.   TDrawPie = procedure(Sender: TObject; var Color: TColor) of object;
  104.   TDrawLabel = procedure(Sender: TObject; Data, DataSum: Double; var DataText: string) of object;
  105.  
  106.   TPieChart = class(ThgCustomChart)
  107.   private
  108.     { private declarations }
  109.     FDataList: TChartDataList;
  110.     FDataSum: Double;
  111.     FUseLabels: boolean;
  112.     FOnDrawPie: TDrawPie;
  113.     FOnDrawLabel: TDrawLabel;
  114.     function GetDrawRect: TRect; override;
  115.     function GetLegendRect(var Offs: integer): TRect;
  116.   protected
  117.     { protected declarations }
  118.     procedure DrawGrid(Rect: TRect); override;
  119.     procedure DrawXScale; override;
  120.     procedure DrawYScale; override;
  121.     procedure DrawDataPoints; override;
  122.     procedure ScaleChart; override;
  123.   public
  124.     { public declarations }
  125.     constructor Create(AOwner: TComponent); override;
  126.     destructor Destroy; override;
  127.     procedure AddData(X: Double; Y: string);
  128.     procedure ClearData;
  129.   published
  130.     { published declarations }
  131.     property UseLabels: boolean read FUseLabels write FUseLabels default true;
  132.     property OnDrawPie: TDrawPie read FOnDrawPie write FOnDrawPie;
  133.     property OnDrawLabel: TDrawLabel read FOnDrawLabel write FOnDrawLabel;
  134.     property Align;
  135.     property BackGround;
  136.     property BorderWidth;
  137.     property ChartColor;
  138.     property Font;
  139.     property Title;
  140.     property TitleFont;
  141.     property XLabel;
  142.     property XYLabelFont;
  143.   end;
  144.  
  145.   TChartType = (ctScatter, ctLine, ctBar, ctXY);
  146.   TAxisType = (atXAxis, atYAxis);
  147.   TNeedLines = procedure(Sender: TObject; var Value: Double;
  148.     var AColor: TColor; var Finished: boolean) of object;
  149.   TScaling = procedure(Sender: TObject; var MinX, MaxX, MinY, MaxY: Double) of object;
  150.   TDrawScales = procedure(Sender: TObject; Axis: TAxisType; Data: Double; var DataText: string) of object;
  151.  
  152.   TXYChart = class(ThgCustomChart)
  153.   private
  154.     { private declarations }
  155.     FChartType: TChartType;
  156.     FDataList: TChartDataList;
  157.     FOnNeedLinesX: TNeedLines;
  158.     FOnNeedLinesY: TNeedLines;
  159.     FOnScaling: TScaling;
  160.     FOnDrawScales: TDrawScales;
  161.     function GetChartRect: TRect; override;
  162.     function GetDrawRect: TRect; override;
  163.     function GetBarWidth: integer;
  164.     function GetDivisionsX(Wid: integer): integer;
  165.     function GetDivisionsY(Ht: integer): integer;
  166.     procedure SetChartType(Value: TChartType);
  167.     procedure SetData(Value: TChartDataList);
  168.   protected
  169.     { protected declarations }
  170.     procedure ScaleChart; override;
  171.     function NormalizePtX(APoint: Double): Double;
  172.     function NormalizePtY(APoint: Double): Double;
  173.     procedure DrawGrid(Rect: TRect); override;
  174.     procedure DrawXScale; override;
  175.     procedure DrawYScale; override;
  176.     procedure DrawDataPoints; override;
  177.     property BarWidth: integer read GetBarWidth;
  178.   public
  179.     { public declarations }
  180.     constructor Create(AOwner: TComponent); override;
  181.     destructor Destroy; override;
  182.     procedure AddData(X, Y: Double);
  183.     procedure Plot;
  184.     procedure ClearData;
  185.     property Data: TChartDataList read FDataList write SetData;
  186.   published
  187.     { published declarations }
  188.     property ChartType: TChartType read FChartType write SetChartType;
  189.     property OnNeedLinesX: TNeedLines read FOnNeedLinesX write FOnNeedLinesX;
  190.     property OnNeedLinesY: TNeedLines read FOnNeedLinesY write FOnNeedLinesY;
  191.     property OnScaling: TScaling read FOnScaling write FOnScaling;
  192.     property OnDrawScales: TDrawScales read FOnDrawScales write FOnDrawScales;
  193.     property Align;
  194.     property BackGround;
  195.     property BorderWidth;
  196.     property ChartColor;
  197.     property Font;
  198.     property Grid;
  199.     property Title;
  200.     property TitleFont;
  201.     property XLabel;
  202.     property YLabel;
  203.     property XYLabelFont;
  204.   end;
  205.  
  206. procedure Register;
  207.  
  208. implementation
  209.  
  210. {$IFDEF WIN32}
  211.   {$R CHARTS.D32}
  212. {$ELSE}
  213.   {$R CHARTS.D16}
  214. {$ENDIF}
  215.  
  216. { ThgCustomChart }
  217. constructor ThgCustomChart.Create(AOwner: TComponent);
  218. begin
  219.   inherited Create(AOwner);
  220.   ControlStyle := ControlStyle + [csOpaque];
  221.   Width := 147;
  222.   Height := 105;
  223.   FTitle := 'HomeGrown Charts';
  224.   FTitleFont := TFont.Create;
  225.   FTitleFont.Name := 'arial';
  226.   FTitleFont.Style := [fsbold];
  227.   FTitleFont.Size := 11;
  228.   FTitleFont.Color := clRed;
  229.   FXLabel := 'X-Axis';
  230.   FYLabel := 'Y-Axis';
  231.   FXYLabelFont := TFont.Create;
  232.   FXYLabelFont.Name := 'arial';
  233.   FXYLabelFont.Size := 8;
  234.   FGrid := gtNone;
  235.   FBackGround := clSilver;
  236.   FChartColor := clWhite;
  237.   FBorderWidth := 1;
  238.   FMinX := 0;
  239.   FMaxX := 50;
  240.   FMinY := 0;
  241.   FMaxY := 50;
  242.   OffScreen := TBitMap.Create;
  243. end;
  244.  
  245. destructor ThgCustomChart.Destroy;
  246. begin
  247.    FTitleFont.Free;
  248.    FXYLabelFont.Free;
  249.    OffScreen.Free;
  250.    inherited Destroy;
  251. end;
  252.  
  253. { property access routines }
  254. procedure ThgCustomChart.SetTitle(Value: TTitleStr);
  255. begin
  256.   FTitle := Value;
  257.   Refresh;
  258. end;
  259.  
  260. procedure ThgCustomChart.SetTitleFont(Value: TFont);
  261. begin
  262.   FTitleFont.Assign(Value);
  263.   Refresh;
  264. end;
  265.  
  266. procedure ThgCustomChart.SetXLabel(Value: TTitleStr);
  267. begin
  268.   FXLabel := Value;
  269.   Refresh;
  270. end;
  271.  
  272. procedure ThgCustomChart.SetYLabel(Value: TTitleStr);
  273. begin
  274.   FYLabel := Value;
  275.   Refresh;
  276. end;
  277.  
  278. procedure ThgCustomChart.SetXYLabelFont(Value: TFont);
  279. begin
  280.   FXYLabelFont.Assign(Value);
  281.   Refresh;
  282. end;
  283.  
  284. procedure ThgCustomChart.SetGrid(Value: TGridType);
  285. begin
  286.   if Value <> FGrid then
  287.   begin
  288.     FGrid := Value;
  289.     Refresh;
  290.   end;
  291. end;
  292.  
  293. procedure ThgCustomChart.SetBackGround(Value: TColor);
  294. begin
  295.   if Value <> FBackGround then
  296.   begin
  297.     FBackGround := Value;
  298.     Refresh;
  299.   end;
  300. end;
  301.  
  302. procedure ThgCustomChart.SetChartColor(Value: TColor);
  303. begin
  304.   if Value <> FChartColor then
  305.   begin
  306.     FChartColor := Value;
  307.     Refresh;
  308.   end;
  309. end;
  310.  
  311. procedure ThgCustomChart.SetBorderWidth(Value: integer);
  312. begin
  313.   if Value <> FBorderWidth then
  314.   begin
  315.     FBorderWidth := Value;
  316.     if FBorderWidth < 1 then FBorderWidth := 1;
  317.     if FBorderWidth > 4 then FBorderWidth := 4;
  318.     Refresh;
  319.   end;
  320. end;
  321.  
  322. { GetChartRect method }
  323. function ThgCustomChart.GetChartRect: TRect;
  324. begin
  325.   with OffScreen do
  326.   begin
  327.     { calulate the TRect for the whole chart }
  328.     Canvas.Font := TitleFont;
  329.     Result.Top := Round(Canvas.TextHeight(FTitle) * 1.5);
  330.     Canvas.Font := XYLabelFont;
  331.     Result.Left := Round(Canvas.TextHeight('Aj') * 1.5);
  332.     Result.Right := Width - Round(Canvas.TextHeight('Aj') * 1.5);
  333.     Result.Bottom := Height - Round(Canvas.TextHeight('Aj') * 1.5);
  334.     Canvas.Font := Font;
  335.   end;
  336. end;
  337.  
  338. { Paint method }
  339. procedure ThgCustomChart.Paint;
  340. var
  341.   hFontNew: HFONT;
  342.   hFontOld: HFONT;
  343.   p: string;
  344. begin
  345.   { paint handles all the drawing unrelated to the actual plot
  346.     ie. title, color, border etc. }
  347.   OffScreen.Width := ClientWidth;
  348.   OffScreen.Height := ClientHeight;
  349.   Font := Font;
  350.   with OffScreen.Canvas do
  351.   begin
  352.     { draw background }
  353.     Brush.Style := bsSolid;
  354.     Brush.Color := FBackGround;
  355.     FillRect(ClientRect);
  356.  
  357.     { draw title }
  358.     Font := TitleFont;
  359.     TextOut((Width-TextWidth(FTitle)) div 2, 1, FTitle);
  360.  
  361.     { draw x-axis label }
  362.     Font := XYLabelFont;
  363.     TextOut((Width-TextWidth(FXLabel)) div 2, Height-TextHeight(FXLabel), FXLabel);
  364.  
  365.     { draw y-axis  label }
  366.     p := Font.Name + #0; { PChar }
  367.     { Creation of the object font and angle }
  368.     hFontNew:= CreateFont(
  369.       { Height      } Font.Height,
  370.       { Width       } 0,
  371.       { Escapement  } 900, { angle of slant of text }
  372.       { Orientation } 0, { expressed in tenth of degree  }
  373.       { Weight      } ord(fsBold in Font.Style) * FW_BOLD,
  374.       { Italic      } ord(fsItalic     in Font.Style),
  375.       { Underline   } ord(fsUnderLine  in Font.Style),
  376.       { StrikeOut   } ord(fsStrikeOut  in Font.Style),
  377.       { Charset     } DEFAULT_CHARSET,
  378.       { OutputPrec. } OUT_DEFAULT_PRECIS,
  379.       { ClipPrec.   } CLIP_DEFAULT_PRECIS,
  380.       { Quality     } DEFAULT_QUALITY,
  381.       { Pitch       } DEFAULT_PITCH + FF_DONTCARE,
  382.       { FaceName    } @p[1]
  383.     );
  384.     { store old font and select new }
  385.     hFontOld := SelectObject(Handle, hFontNew);
  386.     { output text in new font }
  387.     TextOut(1, (Height+TextHeight(FYLabel)) div 2, FYLabel);
  388.     { return the old font }
  389.     SelectObject(Handle, hFontOld);
  390.     { delete the new font }
  391.     DeleteObject(hFontNew);
  392.  
  393.     { scale the chart - abstract }
  394.     ScaleChart;
  395.  
  396.     { draw border }
  397.     Brush.Style := bsSolid;
  398.     Brush.Color := FChartColor;
  399.     Pen.Width := FBorderWidth;
  400.     RoundRect(ChartRect.Left, ChartRect.Top, ChartRect.Right,
  401.       ChartRect.Bottom, 15, 15);
  402.     Pen.Width := 1;
  403.     Font.Color := clBlack;
  404.  
  405.     { draw Grid }
  406.     DrawGrid(ChartRect);
  407.  
  408.     { draw axis scales - abstract }
  409.     DrawXScale;
  410.     DrawYScale;
  411.  
  412.     { draw data points - abstract }
  413.     DrawDataPoints;
  414.   end;
  415.   Canvas.Draw(0, 0, OffScreen);
  416. end;
  417.  
  418. { PaintTo method }
  419. procedure ThgCustomChart.PaintTo(DC: HDC; X, Y: Integer);
  420. var
  421.   SaveIndex: Integer;
  422. begin
  423.   SaveIndex := SaveDC(DC);
  424.   MoveWindowOrg(DC, X, Y);
  425.   IntersectClipRect(DC, 0, 0, Width, Height);
  426.   Perform(WM_ERASEBKGND, DC, 0);
  427.   Perform(WM_PAINT, DC, 0);
  428.   RestoreDC(DC, SaveIndex);
  429. end;
  430.  
  431. { GetComponentImage method }
  432. function ThgCustomChart.GetComponentImage: TBitmap;
  433. begin
  434.   Result := TBitmap.Create;
  435.   try
  436.     Result.Width := ClientWidth;
  437.     Result.Height := ClientHeight;
  438.     Result.Canvas.Lock;
  439.     try
  440.       PaintTo(Result.Canvas.Handle, 0, 0);
  441.     finally
  442.       Result.Canvas.Unlock;
  443.     end;
  444.   except
  445.     Result.Free;
  446.     raise;
  447.   end;
  448. end;
  449.  
  450. { TPieChart }
  451. constructor TPieChart.Create(AOwner: TComponent);
  452. begin
  453.   inherited Create(AOwner);
  454.   // create the data list
  455.   FDataList := TChartDataList.Create;
  456.   // set default values
  457.   Width := 200;
  458.   Height := 150;
  459.   FDataSum := 0;
  460.   FYLabel := '';
  461.   FUseLabels := true;
  462. end;
  463.  
  464. destructor TPieChart.Destroy;
  465. begin
  466.   // free the data list 
  467.   FDataList.Free;
  468.   inherited Destroy;
  469. end;
  470.  
  471. function TPieChart.GetLegendRect(var Offs: integer): TRect;
  472. begin
  473.   // get chart rect
  474.   Result := GetChartRect;
  475.   
  476.   // size it
  477.   Result.Left := Result.Right-Offscreen.Canvas.TextWidth('Abcdefghijklmno')-19;
  478.   Result.Top := Result.Top;
  479.   Result.Bottom := Result.Bottom;
  480.  
  481.   // calc space for legends
  482.   if FDataList.Count <> 0 then
  483.     Offs := (Result.Bottom-Result.Top) div FDataList.Count
  484.   else
  485.     Offs := (Result.Bottom-Result.Top);
  486. end;
  487.  
  488. { GetDrawRect - get area for drawing }
  489. function TPieChart.GetDrawRect: TRect;
  490. var
  491.   Offs: integer;
  492.   CR, LR: TRect;
  493.   dx, dy: integer;
  494. begin
  495.   // get chart rect
  496.   CR := GetChartRect;
  497.   // get legend rect
  498.   LR := GetLegendRect(Offs);
  499.  
  500.   // calculate difference
  501.   SubtractRect(Result, CR, LR);
  502.   // offset for appearance
  503.   Offs := ((5 * Height) div 100);
  504.  
  505.   // calculate drawing rect for pie
  506.   if Result.Right >= Result.Bottom then  // if wider than high
  507.   begin
  508.     Result.Top := Result.Top+Offs;
  509.     Result.Bottom := Result.Bottom-Offs;
  510.     Result.Left := Result.Left+Offs;
  511.     Result.Right := Result.Left+(Result.Bottom - Result.Top);
  512.     dx := ((LR.Left-CR.Left) div 2)-(((Result.Right-Result.Left)+Offs) div 2);
  513.     OffsetRect(Result, dx, 0);
  514.   end else // else higher than wide
  515.   begin
  516.     Result.Top := Result.Top + Offs;
  517.     Result.Left := Result.Left+Offs;
  518.     Result.Right := Result.Right-Offs;
  519.     Result.Bottom := Result.Top+(Result.Right-Result.Left);
  520.     dy := (CR.Bottom div 2)-((Result.Bottom+Offs) div 2);
  521.     OffsetRect(Result, 0, dy);
  522.   end;
  523. end;
  524.  
  525. procedure TPieChart.AddData(X: Double; Y: string);
  526. var
  527.   P: TDataPoint;
  528. begin
  529.   // keep track of data sum
  530.   FDataSum := FDataSum+X;
  531.  
  532.   // create data object
  533.   P := TDataPoint.Create;
  534.   P.X := X; P.Y := 0;
  535.   P.ALabel := Y;
  536.   
  537.   // add it to the list
  538.   FDataList.AddDataPoint(P);
  539.   // don't need to free - the list does that
  540. end;
  541.  
  542. procedure TPieChart.ClearData;
  543. begin
  544.   if FDataList.Count <> 0 then FDataList.Clear; // clear data
  545.   FDataSum := 0; // re-set datasum
  546. end;
  547.  
  548. procedure TPieChart.DrawGrid(Rect: TRect);
  549. begin
  550.   // no grids
  551. end;
  552.  
  553. procedure TPieChart.DrawXScale;
  554. begin
  555.   // do nothing - no scales
  556. end;
  557.  
  558. procedure TPieChart.DrawYScale;
  559. begin
  560.   // do nothing - no scales
  561. end;
  562.  
  563. procedure TPieChart.ScaleChart;
  564. begin
  565.   // do nothing - no extrema
  566. end;
  567.  
  568. procedure TPieChart.DrawDataPoints;
  569. const
  570.   Margin: integer = 5;
  571.   AColor: array[0..16] of TColor = (clAqua, clBlue, clFuchsia, clRed, clLime,
  572.     clYellow, clLtGray, clNavy, clMaroon, clGray, clOlive, clPurple, clSilver,
  573.     clTeal, clDkGray, clWhite, clGreen);
  574. var
  575.   LegendRect: TRect;
  576.   midX, midY: integer;
  577.   SAngle, EAngle: integer;
  578.   i: integer;
  579.   LegendOffs: integer;
  580.   Color: TColor;
  581.   DataText: string;
  582.  
  583.   // get point from angle and length
  584.   function GetPoint(Angle, Length: integer): TPoint;
  585.   var
  586.     sX, sY: double;
  587.   begin
  588.     sX := Cos((Angle / 180.0) * pi);
  589.     sY := Sin((Angle / 180.0) * pi);  // in radians
  590.     Result.X := Round(sX * Length);
  591.     Result.Y := Round(sY * Length);
  592.   end;
  593.  
  594.   // get angle form data/datasum
  595.   function GetAngle(Value: Double): integer;
  596.   var
  597.     Tmp: Double;
  598.   begin
  599.     Tmp := (Value/FDataSum) * 360;
  600.     Result := Round(Tmp);
  601.   end;
  602.  
  603.   // calculate radius in pixels
  604.   function Radius: integer;
  605.   begin
  606.     Result := (DrawRect.Bottom-DrawRect.Top) div 2;
  607.   end;
  608.  
  609.   // draw data labels in pie slice only if they fit in the slice
  610.   procedure DrawValues(SAngle, EAngle: integer; P: TPoint; S: string);
  611.   var
  612.     Points: array[0..2] of TPoint;
  613.     Rgn, TmpRgn: hRgn;
  614.     SA, EA: integer;
  615.   begin
  616.     TmpRgn := 0;
  617.     with Offscreen.Canvas do
  618.     begin
  619.       // offset text starting point so
  620.       // center of text at center of pie
  621.       P.X := P.X - (TextWidth(S) div 2);
  622.       P.Y := P.Y - (TextHeight(S) div 2);
  623.  
  624.       // compute region similar to pie
  625.       SA := SAngle;
  626.       EA := SA + 20;
  627.       if EA > EAngle then EA := EAngle;
  628.       Points[0] := Point(MidX, MidY);
  629.       Points[1] := Point(MidX+GetPoint(SA, Radius).X, MidY-GetPoint(SA, Radius).Y);
  630.       Points[2] := Point(midX+GetPoint(EA, Radius).X, midY-GetPoint(EA, Radius).Y);
  631.       Rgn := CreatePolygonRgn(Points, 3, ALTERNATE); // first slice
  632.       try
  633.         SA := EA;
  634.         while SA <> EAngle do
  635.         begin
  636.           EA := SA + 20;
  637.           if EA > EAngle then EA := EAngle;
  638.           Points[0] := Point(MidX, MidY);
  639.           Points[1] := Points[2];
  640.           Points[2] := Point(midX+GetPoint(EA, Radius).X, midY-GetPoint(EA, Radius).Y);
  641.           try
  642.             TmpRgn := CreatePolygonRgn(Points, 3, ALTERNATE); // next slice
  643.             CombineRgn(Rgn, Rgn, TmpRgn, RGN_OR); // combine slices 
  644.           finally
  645.             DeleteObject(TmpRgn); // free resources
  646.           end;
  647.           SA := EA;
  648.         end;
  649.  
  650.         // Only output values if the text fits the region
  651.         if PtInRegion(Rgn, P.X, P.Y) and PtInRegion(Rgn, P.X+TextWidth(S), P.Y) and
  652.           PtInRegion(Rgn, P.X, P.Y+TextHeight(S)) and
  653.             PtInRegion(Rgn, P.X+TextWidth(S), P.Y+TextHeight(S)) then
  654.           TextOut(P.X, P.Y, S);
  655.       finally
  656.         DeleteObject(Rgn); // free resources
  657.       end;
  658.     end;
  659.   end;
  660.  
  661. begin
  662.   // get rect for drawing legend
  663.   LegendRect := GetLegendRect(LegendOffs);
  664.  
  665.   // draw offscreen for speed
  666.   with OffScreen.Canvas do
  667.   begin
  668.     // calculate center
  669.     midX := ((DrawRect.Right+DrawRect.Left) div 2);
  670.     midY := ((DrawRect.Bottom+DrawRect.Top) div 2);
  671.  
  672.     SAngle := 0; // default value
  673.  
  674.     // draw and fill circle
  675.     Brush.Color := AColor[0];
  676.     Ellipse(DrawRect);
  677.  
  678.     // if we have data then start plotting
  679.     if FDataList.Count <> 0 then
  680.       for i := 0 to FDataList.Count-1 do
  681.       begin
  682.         // calculate end angle from start angle and data
  683.         EAngle := SAngle+GetAngle(FDataList.GetDataPoint(i).X);
  684.  
  685.         Color := AColor[i]; // set color for slice
  686.         if Assigned(FOnDrawPie) then FOnDrawPie(Self, Color); // trigger event
  687.         Brush.Color := Color; // re-set color in case modified
  688.  
  689.         if EAngle <> SAngle then // don't bother drawing pie if angles are equal
  690.           Pie(DrawRect.Left, DrawRect.Top, DrawRect.Right, DrawRect.Bottom,
  691.             MidX+GetPoint(SAngle, Radius).X, MidY-GetPoint(SAngle, Radius).Y,
  692.               midX+GetPoint(EAngle, Radius).X, midY-GetPoint(EAngle, Radius).Y);
  693.  
  694.         // draw data labels
  695.         DataText := FloatToStr(FDataList.GetDataPoint(i).X);
  696.         if Assigned(FOnDrawLabel) then FOnDrawLabel(Self, FDataList.GetDataPoint(i).X, FDataSum, DataText);
  697.         if FUseLabels then
  698.           DrawValues(SAngle, EAngle, Point(MidX+GetPoint((EAngle+SAngle) div 2, Radius div 2).X,
  699.             MidY-GetPoint((EAngle+SAngle) div 2, Radius div 2).Y), DataText);
  700.  
  701.         // draw legend
  702.         Rectangle(LegendRect.Left+Margin, LegendRect.Top+((i+1)*LegendOffs)-(LegendOffs div 2),
  703.           LegendRect.Left+Margin+14, LegendRect.Top+((i+1)*LegendOffs-(LegendOffs div 2)+14));
  704.         Brush.Color := ChartColor;
  705.         TextRect(Rect(LegendRect.Left+Margin+20, LegendRect.Top+((i+1)*LegendOffs-(LegendOffs div 2)),
  706.           LegendRect.Right-5, LegendRect.Bottom-1), LegendRect.Left+Margin+20,
  707.             LegendRect.Top+((i+1)*LegendOffs)-(LegendOffs div 2), FDataList.GetDataPoint(i).ALabel);
  708.  
  709.         // set end angle equal to start angle for next iteration
  710.         SAngle := EAngle;
  711.       end;
  712.   end;
  713. end;
  714.  
  715. { TChartDataList - data structure for the chart data }
  716. destructor TChartDataList.Destroy;
  717. var
  718.   Temp: TObject;
  719. begin
  720.   while Count <> 0 do
  721.   begin
  722.     { make Temp := first Item }
  723.     Temp := Objects[0];
  724.     { free it }
  725.     Temp.Free;
  726.     { delete it }
  727.     Delete(0);
  728.   end;
  729.   { call inherited }
  730.   inherited Destroy;  
  731. end;
  732.  
  733. function TChartDataList.AddDataPoint(Point: TDataPoint): integer;
  734. begin
  735.   Result := AddObject('', Point);
  736. end;
  737.  
  738. function TChartDataList.GetDataPoint(index: integer): TDataPoint;
  739. begin
  740.   Result := Objects[index] as TDataPoint;
  741. end;
  742.  
  743. { TXYChart }
  744. constructor TXYChart.Create(AOwner: TComponent);
  745. begin
  746.   inherited Create(AOwner);
  747.   { create the data list }
  748.   FDataList := TChartDataList.Create;
  749. end;
  750.  
  751. destructor TXYChart.Destroy;
  752. begin
  753.   { free the data list }
  754.   FDataList.Free;
  755.   inherited Destroy;
  756. end;
  757.  
  758. procedure TXYChart.AddData(X, Y: Double);
  759. var
  760.   P: TDataPoint;
  761. begin
  762.   { create data object }
  763.   P := TDataPoint.Create;
  764.   P.X := X; P.Y := Y;
  765.   { add it to the list }
  766.   FDataList.AddDataPoint(P);
  767.   { don't need to free - the list does that }
  768. end;
  769.  
  770. procedure TXYChart.SetChartType(Value: TChartType);
  771. begin
  772.   if Value <> FChartType then
  773.   begin
  774.     FChartType := Value;
  775.     Refresh;
  776.   end;
  777. end;
  778.  
  779. procedure TXYChart.ClearData;
  780. begin
  781.   if FDataList.Count <> 0 then FDataList.Clear;
  782. end;
  783.  
  784. procedure TXYChart.Plot;
  785. begin
  786.   Refresh;
  787. end;
  788.  
  789. procedure TXYChart.SetData(Value: TChartDataList);
  790. begin
  791.   FDataList.Free;
  792.   FDataList.Assign(Value);
  793.   Refresh;
  794. end;
  795.  
  796. { GetChartRect method }
  797. function TXYChart.GetChartRect: TRect;
  798. begin
  799.   Result := inherited GetChartRect;
  800.   Result.Left := Result.Left + OffScreen.Canvas.TextWidth(Format('%6.3f', [FMaxY]));
  801.   Result.Bottom := Result.Bottom - OffScreen.Canvas.TextHeight('Aj');
  802. end;
  803.  
  804. { GetDrawRect - get area for drawing }
  805. function TXYChart.GetDrawRect: TRect;
  806. begin
  807.   { get chart rect }
  808.   Result := GetChartRect;
  809.   { offset inwards 3 and 4 percent }
  810.   Result.Left := Result.Left + ((4 * Width) div 100);
  811.   Result.Top := Result.Top + ((3 * Width) div 100);
  812.   Result.Right := Result.Right - ((4 * Width) div 100);
  813.   Result.Bottom := Result.Bottom - ((3 * Width) div 100);
  814. end;
  815.  
  816. { GetBarWidth - get width of bars for bar chart }
  817. function TXYChart.GetBarWidth: integer;
  818. const
  819.   MaxBarWidth: integer = 15;
  820. begin
  821.   Result := (((DrawRect.Right-DrawRect.Left) div FDataList.Count) div 2);
  822.   if Result > MaxBarWidth then Result := MaxBarWidth;
  823. end;
  824.  
  825. { ScaleChart - calculate extrema }
  826. procedure TXYChart.ScaleChart;
  827.  
  828.   function XMaxima: Double;
  829.   var
  830.     i: integer;
  831.   begin
  832.     Result := FDataList.GetDataPoint(0).X;
  833.     for i := 0 to FDataList.Count-1 do
  834.       if FDataList.GetDataPoint(i).X > Result then Result := FDataList.GetDataPoint(i).X;
  835.   end;
  836.  
  837.   function YMaxima: Double;
  838.   var
  839.     i: integer;
  840.   begin
  841.     Result := FDataList.GetDataPoint(0).Y;
  842.     for i := 0 to FDataList.Count-1 do
  843.       if FDataList.GetDataPoint(i).Y > Result then Result := FDataList.GetDataPoint(i).Y;
  844.   end;
  845.  
  846.   function XMinima: Double;
  847.   var
  848.     i: integer;
  849.   begin
  850.     Result := FDataList.GetDataPoint(0).X;
  851.     for i := 0 to FDataList.Count-1 do
  852.       if FDataList.GetDataPoint(i).X < Result then Result := FDataList.GetDataPoint(i).X;
  853.   end;
  854.  
  855.   function YMinima: Double;
  856.   var
  857.     i: integer;
  858.   begin
  859.     Result := FDataList.GetDataPoint(0).Y;
  860.     for i := 0 to FDataList.Count-1 do
  861.       if FDataList.GetDataPoint(i).Y < Result then Result := FDataList.GetDataPoint(i).Y;
  862.   end;
  863.  
  864.   function TruncFloat(Value: Extended): Extended;
  865.   var
  866.     FloatRec: TFloatRec;
  867.     Temp: string;
  868.   begin
  869.     FloatToDecimal(FloatRec, Value, fvExtended, 12, 9999);
  870.     Temp := FloatToStrF(Value, ffExponent, 1, FloatRec.Exponent);
  871.     Result := StrToFloat(Temp);
  872.   end;
  873.  
  874. begin
  875.   if FDataList.Count <> 0 then
  876.   begin
  877.     // set extreme values - average +/- half the
  878.     // absolute difference between extrema
  879.     FMinX := ((XMaxima+XMinima)/2)-(abs(XMaxima-XMinima)/2);
  880.     FMaxX := ((XMaxima+XMinima)/2)+(abs(XMaxima-XMinima)/2);
  881.     FMinY := ((YMaxima+YMinima)/2)-(abs(YMaxima-YMinima)/2);
  882.     FMaxY := ((YMaxima+YMinima)/2)+(abs(YMaxima-YMinima)/2);
  883.   end;
  884.   if Assigned(FOnScaling) then FOnScaling(Self, FMinX, FMaxX, FMinY, FMaxY);
  885.   // make sure minima and maxima are not equal
  886.   if FMinX-FMaxX = 0 then
  887.   begin
  888.     FMinX := FMinX-0.1;
  889.     FMaxX := FMaxX+0.1;
  890.   end;
  891.   if FMinY-FMaxY = 0 then
  892.   begin
  893.     FMinY := FMinY-0.1;
  894.     FMaxY := FMaxY+0.1;
  895.   end;
  896. end;
  897.  
  898. function TXYChart.GetDivisionsX(Wid: integer): integer;
  899. begin
  900.   Result := Wid div (2 * OffScreen.Canvas.TextWidth(Format('%6.3f', [FMaxX])));
  901.   if Result >= 5 then Result := 10;
  902. end;
  903.  
  904. function TXYChart.GetDivisionsY(Ht: integer): integer;
  905. begin
  906.   Result := Ht div (2 * (OffScreen.Canvas.TextHeight('Aj')));
  907.   if Result >= 5 then Result := 10;
  908. end;
  909.  
  910. { procedure to draw grids if desired }
  911. procedure TXYChart.DrawGrid(Rect: TRect);
  912. var
  913.   i: integer;
  914.   Xinterval: integer;
  915.   Yinterval: integer;
  916.   GraphWd: integer;
  917.   GraphHt: integer;
  918.  
  919.   procedure DoDrawGridX(Amount: integer);
  920.   begin
  921.     with OffScreen.Canvas Do
  922.     begin
  923.       MoveTo(DrawRect.Right-Amount, ChartRect.Top+BorderWidth);
  924.       LineTo(DrawRect.Right-Amount, ChartRect.Bottom-BorderWidth);
  925.     end;
  926.   end;
  927.  
  928.   procedure DoDrawGridY(Amount: integer);
  929.   begin
  930.     with OffScreen.Canvas Do
  931.     begin
  932.       MoveTo(ChartRect.Left+BorderWidth, DrawRect.Top + Amount);
  933.       LineTo(ChartRect.Right-BorderWidth, DrawRect.Top + Amount);
  934.     end;
  935.   end;
  936.  
  937. begin
  938.   if FGrid = gtNone then Exit;
  939.   GraphWd := (DrawRect.Right - DrawRect.Left);
  940.   GraphHt := (DrawRect.Bottom - DrawRect.Top);
  941.   Xinterval := GetDivisionsX(GraphWd);
  942.   Yinterval := GetDivisionsY(GraphHt);
  943.   with OffScreen.Canvas do
  944.   begin
  945.     if FChartColor <> clSilver then Pen.Color := clSilver
  946.       else Pen.Color := clGray;
  947.     case FGrid of
  948.       gtBoth, gtVert:
  949.         case XInterval of
  950.         0,1,2: begin
  951.             DoDrawGridX(GraphWd);
  952.             DoDrawGridX(GraphWd Div 2);
  953.             DoDrawGridX(0);
  954.           end;
  955.         3: begin
  956.             DoDrawGridX(0);
  957.             DoDrawGridX(GraphWd Div 4);
  958.             DoDrawGridX(GraphWd Div 2);
  959.             DoDrawGridX(GraphWd Div 4*3);
  960.             DoDrawGridX(GraphWd);
  961.           end;
  962.         4: begin
  963.           for I := 0 to 10 do
  964.             if not Odd(I) then
  965.               DoDrawGridX(Round(GraphWd * I/10));
  966.           end;
  967.         else
  968.           for I := 0 To 10 do
  969.             DoDrawGridX(Round(GraphWd * I/10));
  970.         end;
  971.     end;
  972.     case FGrid of
  973.       gtBoth, gtHorz:
  974.         case YInterval of
  975.         0,1,2: begin
  976.             DoDrawGridY(GraphHt);
  977.             DoDrawGridY(GraphHt Div 2);
  978.             DoDrawGridY(0);
  979.           end;
  980.         3: begin
  981.             DoDrawGridY(0);
  982.             DoDrawGridY(GraphHt Div 4);
  983.             DoDrawGridY(GraphHt Div 2);
  984.             DoDrawGridY(GraphHt Div 4*3);
  985.             DoDrawGridY(GraphHt);
  986.           end;
  987.         4: begin
  988.           for I := 0 to 10 do
  989.             if not Odd(I) then
  990.               DoDrawGridY(Round(GraphHt * I/10));
  991.           end;
  992.         else
  993.           for I := 0 To 10 do
  994.             DoDrawGridY(Round(GraphHt * I/10));
  995.         end;
  996.     end;
  997.   end;
  998. end;
  999.  
  1000. procedure TXYChart.DrawXScale;
  1001. var
  1002.   i: integer;
  1003.   interval: integer;
  1004.   GraphWid: integer;
  1005.   Range: Double;
  1006.  
  1007.   procedure DoDrawScale(Offs: integer; Value: Double);
  1008.   var
  1009.     S: string;
  1010.     R: TRect;
  1011.     X: integer;
  1012.   begin
  1013.     with OffScreen.Canvas Do
  1014.     begin
  1015.       MoveTo(DrawRect.Right-Offs, ChartRect.Bottom-BorderWidth-5);
  1016.       LineTo(DrawRect.Right-Offs, ChartRect.Bottom-BorderWidth);
  1017.       S := FloatToStr(Value);
  1018.       if Assigned(FOnDrawScales) then FOnDrawScales(Self, atXAxis, Value, S);
  1019.       R := Rect(DrawRect.Right-Offs-(((DrawRect.Right-DrawRect.Left) div interval) div 2),
  1020.         ChartRect.Bottom+3, DrawRect.Right-Offs+(((DrawRect.Right-DrawRect.Left) div interval) div 2),
  1021.           ChartRect.Bottom+TextHeight(S)+3);
  1022.       X := DrawRect.Right-Offs-(TextWidth(S) div 2);
  1023.       if X < DrawRect.Right-Offs-(((DrawRect.Right-DrawRect.Left) div interval) div 2)
  1024.         then X := DrawRect.Right-Offs-(((DrawRect.Right-DrawRect.Left) div interval) div 2);
  1025.       TextRect(R, X, ChartRect.Bottom+3, S);
  1026.     end;
  1027.   end;
  1028.  
  1029. begin
  1030.   GraphWid := (DrawRect.Right - DrawRect.Left);
  1031.   Range := FMaxX-FMinX;
  1032.   interval := GetDivisionsX(GraphWid);
  1033.   with OffScreen.Canvas do
  1034.   begin
  1035.     Pen.Color := clBlack;
  1036.     Brush.Color := FBackGround;
  1037.     case interval of
  1038.      0: begin
  1039.           MoveTo(DrawRect.Left+(GraphWid div 2), ChartRect.Bottom-BorderWidth-5);
  1040.           LineTo(DrawRect.Left+(GraphWid div 2), ChartRect.Bottom-BorderWidth);
  1041.         end;
  1042.      1: DoDrawScale(GraphWid Div 2, Range / 2 + FMinX);
  1043.      2: begin
  1044.           DoDrawScale(GraphWid, FMinX);
  1045.           DoDrawScale(GraphWid Div 2, Range / 2+FMinX);
  1046.           DoDrawScale(0, FMaxX);
  1047.         end;
  1048.      3: begin
  1049.           DoDrawScale(0, FMaxX);
  1050.           DoDrawScale(GraphWid Div 4, Range / 4*3+FMinX);
  1051.           DoDrawScale(GraphWid Div 2, Range / 2+FMinX);
  1052.           DoDrawScale(GraphWid Div 4*3, Range / 4+FMinX);
  1053.           DoDrawScale(GraphWid, FMinX);
  1054.         end;
  1055.      4: begin
  1056.           for I := 0 to 10 do
  1057.             if not Odd(I) then
  1058.               DoDrawScale(Round(GraphWid * I/10), Range*(10-I)/10+FMinX);
  1059.         end;
  1060.      else
  1061.         for I := 0 To 10 do
  1062.           DoDrawScale(Round(GraphWid* I/10), Range*(10-I)/10+FMinX);
  1063.     end; {Case}
  1064.   end;
  1065. end;
  1066.  
  1067. procedure TXYChart.DrawYScale;
  1068. var
  1069.   i: integer;
  1070.   interval: LongInt;
  1071.   GraphHt: integer;
  1072.   Range: Double;
  1073.  
  1074.   procedure DoDrawScale(Offs: integer; Value: Double);
  1075.   var
  1076.     S: string;
  1077.   begin
  1078.     with OffScreen.Canvas Do
  1079.     begin
  1080.       MoveTo(ChartRect.Left, DrawRect.Top+Offs);
  1081.       LineTo(ChartRect.Left+5, DrawRect.Top+Offs);
  1082.       S := FloatToStr(Value);
  1083.       if Assigned(FOnDrawScales) then FOnDrawScales(Self, atYAxis, Value, S);
  1084.       TextOut(ChartRect.Left-3-TextWidth(S), DrawRect.Top+Offs-(TextHeight(S) div 2), S);
  1085.     end;
  1086.   end;
  1087.  
  1088. begin
  1089.   GraphHt := (DrawRect.Bottom - DrawRect.Top);
  1090.   Range := FMaxY-FMinY;
  1091.   interval := GetDivisionsY(GraphHt);
  1092.   with OffScreen.Canvas do
  1093.   begin
  1094.     Pen.Color := clBlack;
  1095.     Brush.Color := FBackGround;
  1096.     case interval of
  1097.      0: begin
  1098.           MoveTo(ChartRect.Left, DrawRect.Top + (GraphHt div 2));
  1099.           LineTo(ChartRect.Left+5, DrawRect.Top + (GraphHt div 2));
  1100.         end;
  1101.      1: DoDrawScale(GraphHt div 2,Range / 2+FMinY);
  1102.      2: begin
  1103.           DoDrawScale(GraphHt, FMinY);
  1104.           DoDrawScale(GraphHt div 2, Range / 2+FMinY);
  1105.           DoDrawScale(0, FMaxY);
  1106.         end;
  1107.      3: begin
  1108.           DoDrawScale(GraphHt, FMinY);
  1109.           DoDrawScale(GraphHt div 4,Range / 4*3+FMinY);
  1110.           DoDrawScale(GraphHt div 2,Range / 2+FMinY);
  1111.           DoDrawScale(GraphHt div 4*3,Range / 4+FMinY);
  1112.           DoDrawScale(0, FMaxY);
  1113.         end;
  1114.      4: begin
  1115.           for I := 0 to 10 do
  1116.             if not Odd(I) then
  1117.               DoDrawScale(Round(GraphHt * I/10), Range*(10-I)/10+FMinY);
  1118.         end;
  1119.      else
  1120.         for I := 0 To 10 do
  1121.           DoDrawScale(Round(GraphHt* I/10), Range*(10-I)/10+FMinY);
  1122.     end; {Case}
  1123.   end;
  1124. end;
  1125.  
  1126. { NormalizePtX - transform  X-axis data to chart dimensions }
  1127. function TXYChart.NormalizePtX(APoint: Double): Double;
  1128. var
  1129.   W: integer;
  1130. begin
  1131.   Result := APoint;
  1132.   W := DrawRect.Right-DrawRect.Left;
  1133.   { if axis range <> 0 - otherwise div by zero }
  1134.   if FMaxX-FMinX <> 0 then
  1135.     { make data point a relative fraction of
  1136.       the chart draw rect }
  1137.     Result := (APoint-FMinX)*W/(FMaxX-FMinX);
  1138. end;
  1139.  
  1140. { NormalizePtY - transform  Y-axis data to chart dimensions }
  1141. function TXYChart.NormalizePtY(APoint: Double): Double;
  1142. var
  1143.   H: integer;
  1144. begin
  1145.   Result := APoint;
  1146.   H := DrawRect.Bottom-DrawRect.Top;
  1147.   { if axis range <> 0 - otherwise div by zero }
  1148.   if FMaxX-FMinX <> 0 then
  1149.     { make data point a relative fraction of
  1150.       the chart draw rect }
  1151.     Result := (APoint-FMinY)*H/(FMaxY-FMinY);
  1152. end;
  1153.  
  1154. procedure TXYChart.DrawDataPoints;
  1155. var
  1156.   i: integer;
  1157.   X, Y: Double;
  1158.   Finished: boolean;
  1159.   AColor: TColor;
  1160.   Rgn: hRgn;
  1161. begin
  1162.   with OffScreen.Canvas do
  1163.   begin
  1164.     Pen.Color := clBlack;
  1165.     if FDataList.Count <> 0 then
  1166.     begin
  1167.       { move pen to 1st data point }
  1168.       MoveTo(DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(0).X)),
  1169.         DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(0).Y)));
  1170.       { iterate through data }
  1171.       for i := 0 to FDataList.Count-1 do
  1172.       begin
  1173.         Rgn := CreateRectRgn(DrawRect.Left-5, DrawRect.Top-5, DrawRect.Right+5, DrawRect.Bottom+5);
  1174.         SelectClipRgn(OffScreen.Canvas.Handle, Rgn);
  1175.         case FChartType of
  1176.           ctLine: LineTo(DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(i).X)),
  1177.             DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)));
  1178.           ctBar: begin
  1179.             Brush.Style := bsSolid;
  1180.             Brush.Color := clNavy;
  1181.             Rectangle(DrawRect.Left+Ceil(NormalizePtX(FDataList.GetDataPoint(i).X))-BarWidth,
  1182.               DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)),
  1183.                 DrawRect.Left+Floor(NormalizePtX(FDataList.GetDataPoint(i).X))+BarWidth,
  1184.                   DrawRect.Bottom);
  1185.           end;
  1186.           ctScatter: Ellipse(DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(i).X)-3),
  1187.             DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)-3),
  1188.               DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(i).X)+3),
  1189.                 DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)+3));
  1190.           ctXY: begin
  1191.             LineTo(DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(i).X)),
  1192.               DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)));
  1193.             Ellipse(DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(i).X)-3),
  1194.               DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)-3),
  1195.                 DrawRect.Left+Round(NormalizePtX(FDataList.GetDataPoint(i).X)+3),
  1196.                   DrawRect.Bottom-Round(NormalizePtY(FDataList.GetDataPoint(i).Y)+3));
  1197.           end;
  1198.         end;
  1199.         DeleteObject(Rgn);
  1200.       end;
  1201.       { Set finished to true }
  1202.       Finished := true;
  1203.       { set tag - counter - to 0 }
  1204.       Self.Tag := 0;
  1205.       { set X and Y to values below Minima }
  1206.       X := FMinX-10;
  1207.       Y := FMinY-10;
  1208.       repeat
  1209.         if Assigned(FOnNeedLinesX) then
  1210.         begin
  1211.           { trigger event }
  1212.           FOnNeedLinesX(Self, X, AColor, Finished);
  1213.           X := NormalizePtX(X);
  1214.           if (DrawRect.Left+Round(X) < DrawRect.Right) and
  1215.             (DrawRect.Left+Round(X) > DrawRect.Left) then with OffScreen.Canvas do
  1216.           begin
  1217.             Pen.Color := AColor;
  1218.             { draw line }
  1219.             MoveTo(DrawRect.Left+Round(X), DrawRect.Top);
  1220.             LineTo(DrawRect.Left+Round(X), DrawRect.Bottom);
  1221.           end;
  1222.           { increment tag - counter }
  1223.           Self.Tag := Self.Tag + 1;
  1224.         end;
  1225.       until Finished = true;
  1226.       { reset counter - finished is already true }
  1227.       Self.Tag := 0;
  1228.       repeat
  1229.         if Assigned(FOnNeedLinesY) then
  1230.         begin
  1231.           FOnNeedLinesY(Self, Y, AColor, Finished);
  1232.           Y := NormalizePtY(Y);
  1233.           if (DrawRect.Bottom-Round(Y) > DrawRect.Top) and
  1234.             (DrawRect.Bottom-Round(Y) < DrawRect.Bottom) then with OffScreen.Canvas do
  1235.           begin
  1236.             Pen.Color := AColor;
  1237.             { draw line }
  1238.             MoveTo(DrawRect.Left, DrawRect.Bottom-Round(Y));
  1239.             LineTo(DrawRect.Right, DrawRect.Bottom-Round(Y));
  1240.           end;
  1241.           { increment tag - counter }
  1242.           Self.Tag := Self.Tag + 1;
  1243.         end;
  1244.       until Finished = true;
  1245.       { reset pen color }
  1246.       Pen.Color := clBlack;
  1247.     end;
  1248.   end;
  1249. end;
  1250.  
  1251. { register component on HomeGrown page }
  1252. procedure Register;
  1253. begin
  1254.   RegisterComponents('HomeGrown', [TPieChart]);
  1255.   RegisterComponents('HomeGrown', [TXYChart]);
  1256. end;
  1257.  
  1258. end.